// Package validator
// Created by GoLand
// @User: lenora
// @Date: 2024/8/28
// @Time: 16:59

package validator

import (
	"camp/microbook/pkg/logger"
	"camp/microbook/pkg/migrator"
	"camp/microbook/pkg/migrator/events"
	"context"
	"errors"
	"github.com/ecodeclub/ekit/slice"
	"golang.org/x/sync/errgroup"
	"gorm.io/gorm"
	"time"
)

type Validator[T migrator.Entity] struct {
	base   *gorm.DB
	target *gorm.DB

	l         logger.Logger
	producer  events.Producer
	batchSize int
}

func (v *Validator[T]) Validate(ctx context.Context) error {
	// 可并行,也可逐一执行
	var eg errgroup.Group

	eg.Go(func() error {
		return v.validateBase2Target(ctx)
	})

	eg.Go(func() error {
		return v.validateTarget2Base(ctx)
	})

	err := eg.Wait()
	return err
}

func (v *Validator[T]) validateBase2Target(ctx context.Context) error {
	offset := 0
	for {
		//查找base中的数据与target中的数据,两者比较是否一致
		//根据一致情况执行对应的逻辑
		var src T
		err := v.base.WithContext(ctx).Order("id").Offset(offset).First(&src).Error
		offset++
		if errors.Is(err, gorm.ErrRecordNotFound) {
			//数据查询结束
			return nil
		} else if err != nil {
			//	查询失败
			v.l.Error("[base => target]get base data failed", logger.Error(err))
			continue
		}
		var dst T
		err = v.target.WithContext(ctx).Where("id = ?", src.Id()).First(&dst).Error
		switch err {
		case gorm.ErrRecordNotFound:
			v.notify(src.Id(), events.EventTypeTargetMissing)
		case nil:
			equal := dst.CompareTo(src)
			if !equal {
				v.notify(src.Id(), events.EventTypeNEQ)
			}
		default:
			//	查询失败
			v.l.Error("[base => target]get target data failed",
				logger.Error(err),
				logger.Uint64("id", src.Id()))
			continue
		}
	}
}

func (v *Validator[T]) validateTarget2Base(ctx context.Context) error {
	var offset = 0
	for {
		//	target表批量查询一部分数据
		var targets []T
		err := v.target.WithContext(ctx).Select("id").
			Order("id").
			Offset(offset).
			Limit(v.batchSize).
			Find(&targets).Error
		offset = offset + v.batchSize
		if errors.Is(err, gorm.ErrRecordNotFound) || len(targets) <= 0 {
			//无数据 数据查询结束
			return nil
		}
		if err != nil {
			v.l.Error("[target => base]get target data failed", logger.Error(err))
			continue
		}

		//	base表中校验这部分id
		var bases []T
		ids := slice.Map[T, uint64](targets, func(idx int, src T) uint64 {
			return src.Id()
		})

		err = v.base.WithContext(ctx).
			Select("id").
			Where("id in ?", ids).
			Find(&bases).Error
		if errors.Is(err, gorm.ErrRecordNotFound) || len(bases) <= 0 {
			//	无对应数据,则都需要发送base_missing消息
			v.notifyBaseMissing(targets)
		}
		if err != nil {
			v.l.Error("[target => base]get base data failed", logger.Error(err))
			continue
		}

		//	求差集,利用slice.DiffSet()方法求出target中存在但base中不存在的数据
		diff := slice.DiffSetFunc(targets, bases, func(src, dst T) bool {
			return src.Id() == dst.Id()
		})

		//	遍历差集中的数据,发送base_missing消息
		v.notifyBaseMissing(diff)

		//	target查询到的数据量 < batchSize时,代表数据已是最后一批,可结束循环
		if len(targets) < v.batchSize {
			return nil
		}
	}

}

func (v *Validator[T]) notify(id uint64, typ string) {
	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
	defer cancel()
	err := v.producer.ProduceInconsistentEvent(ctx)
	if err != nil {
		v.l.Error("send notify msg failed",
			logger.Error(err),
			logger.Uint64("id", id),
			logger.String("type", typ))
	}
}

func (v *Validator[T]) notifyBaseMissing(data []T) {
	for _, datum := range data {
		v.notify(datum.Id(), events.EventTypeBaseMissing)
	}
}
